package ca.qc.bdeb.jeuxdesboites.model;

import ca.qc.bdeb.jeuxdesboites.commun.Coup;
import ca.qc.bdeb.utilitaires.Direction;
import ca.qc.bdeb.jeuxdesboites.commun.IDJoueurs;
import java.awt.Point;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Eric Wenaas
 */
public final class TableauJeu implements Cloneable {

    private int largeur;
    private int hauteur;
    private Boite boites[][];
    private int nombreDeBoitesFermees;

    TableauJeu(int width, int height) {
        this.largeur = width;
        this.hauteur = height;
        boites = new Boite[width][height];
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                boites[i][j] = new Boite();
            }
        }
        nombreDeBoitesFermees = 0;
    }

    public int getLargeur() {
        return largeur;
    }

    public int getHauteur() {
        return hauteur;
    }
    
    public boolean isBoiteFerme(int x, int y) {
        return boites[x][y].isFermee();
    }
    
    public IDJoueurs getProprietaire(int xPosition, int yPosition) {
        return boites[xPosition][yPosition].getProprietaire();
    }

    public boolean isFermee(int xPosition, int yPosition, Direction direction) {
        return boites[xPosition][yPosition].isFermeeDirection(direction);
    }
    
    public boolean isComplet() {
        return nombreDeBoitesFermees == largeur * hauteur;
    }

    boolean jouerCoup(IDJoueurs id, Coup unCoup) {
        boolean boiteFermee = false;
        Boite laBoite = boites[unCoup.positionX][unCoup.positionY];
        Coup coupSemblable = obtenirCoupCorrespondant(unCoup);
        Boite autreBoite = boites[coupSemblable.positionX][coupSemblable.positionY];
        if (laBoite.isFermeeDirection(unCoup.direction)) {
            throw new RuntimeException("Barre déjà fermée: " + unCoup.toString());
        } else if (autreBoite != null  &&  autreBoite.isFermeeDirection(coupSemblable.direction)) {
            throw new RuntimeException("Barre déjà fermée: " + unCoup.toString());
        }
        laBoite.setFermeeDirection(unCoup.direction, true);
        if (autreBoite != null) {
            autreBoite.setFermeeDirection(coupSemblable.direction, true);
        }
        if (laBoite.isFermee()) {
            laBoite.setProprietaire(id);
            boiteFermee = true;
            nombreDeBoitesFermees++;
        }
        if (autreBoite != laBoite && autreBoite.isFermee()) {
            autreBoite.setProprietaire(id);
            boiteFermee = true;
            nombreDeBoitesFermees++;
        }
        return boiteFermee;
    }

    void dejouerCoup(Coup unCoup) {
        Boite laBoite = boites[unCoup.positionX][unCoup.positionY];
        Coup coupSemblable = obtenirCoupCorrespondant(unCoup);
        Boite autreBoite = boites[coupSemblable.positionX][coupSemblable.positionY];
        
        if (! laBoite.isFermeeDirection(unCoup.direction)) {
            throw new RuntimeException("Barre n'est pas fermée: " + unCoup.toString());
        } else if (autreBoite != laBoite  &&  ! autreBoite.isFermeeDirection(coupSemblable.direction)) {
            throw new RuntimeException("Barre n'est pas fermée: " + unCoup.toString());
        }

        if (laBoite.isFermee()) {
            laBoite.setProprietaire(null);
            nombreDeBoitesFermees--;
        }
        if (autreBoite != laBoite && autreBoite.isFermee()) {
            autreBoite.setProprietaire(null);
            nombreDeBoitesFermees--;
        }
        
        laBoite.setFermeeDirection(unCoup.direction, false);
        if (autreBoite != null) {
            autreBoite.setFermeeDirection(coupSemblable.direction, false);
        }
    }
    
    int getNombrePoints(IDJoueurs joueur) {
        int reponse = 0;
        for (int x = 0; x < largeur; x++) {
            for (int y = 0; y < hauteur; y++) {
                if (getProprietaire(x, y) == joueur) {
                    reponse++;
                }
            }
        }
        return reponse;

    }

    Coup obtenirCoupCorrespondant(Coup unCoup) {
        int x = unCoup.positionX;
        int y = unCoup.positionY;
        Direction d = unCoup.direction;
        Coup reponse;

        if (x == 0 && d.equals(Direction.GAUCHE)) {
            reponse = unCoup;
        } else if (y == 0 && d.equals(Direction.HAUT)) {
            reponse = unCoup;
        } else if (x == largeur - 1 && d.equals(Direction.DROIT)) {
            reponse = unCoup;
        } else if (y == hauteur - 1 && d.equals(Direction.BAS)) {
            reponse = unCoup;
        } else {
            switch (d) {
                case BAS:
                    y = y + 1;
                    d = Direction.HAUT;
                    break;
                case DROIT:
                    x = x + 1;
                    d = Direction.GAUCHE;
                    break;
                case GAUCHE:
                    x = x - 1;
                    d = Direction.DROIT;
                    break;
                case HAUT:
                    y = y - 1;
                    d = Direction.BAS;
                    break;
            }
            reponse = new Coup(x, y, d);
        }
        return reponse;
    }
    
    Point obtenirPointDirection(Point initial, Direction d) {
        Point nouveauPoint = null;
        int nouveauX = initial.x;
        int nouveauY = initial.y;
        switch (d) {
            case HAUT:
                nouveauY--;
                break;
            case BAS:
                nouveauY++;
                break;
            case GAUCHE:
                nouveauX--;
                break;
            case DROIT:
                nouveauX++;
                break;
        }
        if (coordonneesValides(nouveauX, nouveauY)) {
            nouveauPoint = new Point(nouveauX, nouveauY);
        }
        return nouveauPoint;
    }
    
    private boolean coordonneesValides(int x, int y) {
        return x >= 0 && x < largeur &&
               y >= 0 && y < hauteur;
    }

    @Override
    protected Object clone() {
        TableauJeu copie = null;
        try {
            copie = (TableauJeu) super.clone();
            copie.largeur = largeur;
            copie.hauteur = hauteur;
            copie.nombreDeBoitesFermees = nombreDeBoitesFermees;
            copie.boites = new Boite[largeur][hauteur];
            for (int i = 0; i < largeur; i++) {
                for (int j = 0; j < hauteur; j++) {
                    Boite nouvelleBoite = new Boite();
                    nouvelleBoite.setProprietaire(boites[i][j].getProprietaire());
                    for (Direction d : Direction.values()) {
                        boolean fermeDansOriginal = boites[i][j].isFermeeDirection(d);
                        nouvelleBoite.setFermeeDirection(d, fermeDansOriginal);
                        copie.boites[i][j] = nouvelleBoite;
                    }
                }
            }
        } catch (CloneNotSupportedException ex) {
            Logger.getLogger(TableauJeu.class.getName()).log(Level.SEVERE, null, ex);
        }
        // TableauJeu copie = new TableauJeu(hauteur, largeur);
        return copie;
    }

    Boite getBoite(int x, int y) {
        return boites[x][y];
    }
    
        /*
     * Un coup candidat est une barre où personne n'a joué.
     */
    ArrayList<Coup> determinerCandidats() {
        ArrayList<Coup> reponse = new ArrayList<>();

        // Ajoute en haut de la première rangée
        for (int x = 0; x < largeur; x++) {
            if (! isFermee(x, 0, Direction.HAUT)) {
                reponse.add(new Coup(x, 0, Direction.HAUT));
            }
        }

        // Ajout pour les autres directions
        for (int x = 0; x < largeur; x++) {
            for (int y = 0; y < hauteur; y++) {
                // Première colonne
                if (x == 0 && ! isFermee(x, y, Direction.GAUCHE)) {
                    reponse.add(new Coup(x, y, Direction.GAUCHE));
                }
                if (! isFermee(x, y, Direction.DROIT)) {
                    reponse.add(new Coup(x, y, Direction.DROIT));
                }
                if (! isFermee(x, y, Direction.BAS)) {
                    reponse.add(new Coup(x, y, Direction.BAS));
                }
            }
        }
        return reponse;
    }

    void afficher(PrintStream out) {
        afficherCoordonnees(out);
        afficherLigneSuperieure(out);
        for (int y=0; y<largeur; y++) {
            afficherGaucheDroite(out, y);
            afficherBas(out, y);
        }
    }
    
    private void afficherCoordonnees(PrintStream out) {
        out.print("    ");
        for (int x=0; x<largeur; x++) {
            out.print(x);
            out.print("  ");
        }
        out.println();
    }
    
    private void afficherLigneSuperieure(PrintStream out) {
        out.print("  #");
        for (int x=0; x<largeur; x++) {
            if (getBoite(x, 0).isFermeeDirection(Direction.HAUT)) {
                out.print("--");
            } else {
                out.print("  ");
            }
            out.print("#");
        }
        out.println();
    }
    
    private void afficherGaucheDroite(PrintStream out, int y) {
        out.print(y);
        out.print(" ");
        if (getBoite(0, y).isFermeeDirection(Direction.GAUCHE)) {
            out.print("|");
        } else {
            out.print(" ");
        }
        for (int x = 0; x < largeur; x++) {
            Boite laBoite = getBoite(x, y);
            IDJoueurs proprietaire = laBoite.getProprietaire();
            if (proprietaire == null) {
                out.print("  ");
            } else if (proprietaire.equals(IDJoueurs.JOUEUR_HUMAIN)) {
                out.print("HU");
            } else if (proprietaire.equals(IDJoueurs.JOUEUR_ARTIFICIEL)) {
                out.print("AI");
            }

            if (laBoite.isFermeeDirection(Direction.DROIT)) {
                out.print("|");
            } else {
                out.append(" ");
            }
        }
        out.println();
    }
            
    
    private void afficherBas(PrintStream out, int y) {
        out.print("  #");
        for (int x = 0; x < largeur; x++) {
            // Haut et bas
            if (getBoite(x, y).isFermeeDirection(Direction.BAS)) {
                out.print("--");
            } else {
                out.print("  ");
            }
            out.print("#");
        }
        out.println();
    }
 
}
